import { IDraw } from "./draw.API";
import type { Object, FeatureStore } from './draw.constant'
import { EMode } from './draw.constant'
import type {
    Feature,
    GeoJSON,
    FeatureCollection,
    Geometry,
    Point,
    Position,
    MultiPoint,
    MultiLineString,
    MultiPolygon,
    Polygon,
    LineString,
    GeoJsonProperties
} from './draw.constant';
import style from './draw.style' // 绘图样式

/**
 * 状态管理
 * layer-id 管理
 */

export class Draw implements IDraw {
    private mapbox: any // mapbox

    private currentMode: EMode = EMode.STATIC //模式状态

    private drawLayerId: number | '' = '' // id

    private drawLayerName: string = '' // name

    private featureStore: FeatureStore = {} // 所有 features

    /**
     * 构造函数
     * @param mapbox 地图对象
     */
    constructor(mapbox: any) {
        this.mapbox = mapbox // mapbox
        // this.mapbox = mapbox.map // WebGIS
    }

    /**
     * 事件存储对象
     */
    private initEvent = () => (
        <{ [key: string]: Function }>
        {
            before: () => console.log('绘制开始前'),
            after: () => console.log('绘制结束后'),
            draw: () => console.log('绘制中')
        })

    private event = this.initEvent()

    /**
     * 挂载事件
     * @param {'before' | 'after' | 'draw'} type 开始前、绘制中、结束后
     * @param callback 回调
     */
    public on = (type: 'before' | 'after' | 'draw', callback: Function): Function => this.event[type] = callback

    /**
     * 解绑事件
     * @param {'before' | 'after' | 'draw' | undefined} type 单独解绑或全部解绑
     */
    public off = (type?: 'before' | 'after' | 'draw'): void => {
        const tempObj = this.initEvent()
        type
            ? (this.event[type] = tempObj[type])
            : (this.event = tempObj)
    }

    /**
     * 绘制点线面功能，右键结束或者点击上个点结束；
     * @param mode 绘制点、线、面模式
     * @param id 绘制 id
     * @returns 返回完整layer名
     */
    public startDraw = (mode: EMode, id: number): string => {
        this.event.before()
        this.currentMode = mode // 模式切换
        this.drawLayerId = id // id
        this.drawLayerName = mode + '_' + id // name
        this.mapbox.getCanvas().style.cursor = 'crosshair' // 鼠标指针
        this.mapbox.on('click', this.draw2map) // 触发绘制
        this.mapbox.on('contextmenu', this.endDraw) // 右键退出绘制
        return this.drawLayerName
    }

    /**
     * 可以设置初始数据geojson；
     * @param {FeatureCollection} featureCollection 初始数据
     */
    public setFeatureCollection = (featureCollection: FeatureCollection): void => {
        this.mapbox.on('load', () => {
            const id = 'default_geojson'
            // const point = ['MultiPoint', 'Point']
            // const line = ['LineString', 'MultiLineString']
            // const polygn = ['Polygon', 'MultiPolygon']
            featureCollection.features.map((v: Feature, i: number) => {
                const { type } = v.geometry
                const name = id + '_' + i
                this.addSource(name, v)
                this.add2AllFeature(name, v)
                type.includes('Point') && this.addLayer.circle(name, undefined, v.properties)
                type.includes('LineString') && this.addLayer.line(name, undefined, v.properties)
                type.includes('Polygon') && this.addLayer.fill(name, undefined, v.properties)
            })
        })
    }

    /**
     * 可以获取到全部绘制数据；
     * @returns {Geojson} tempObj
     */
    public getAll = (): FeatureStore => {
        // console.log(this._ids)
        // const features: GeoJSON = this.mapbox.queryRenderedFeatures({ layers: this._ids })
        const tempArr = Object.keys(this.featureStore).filter((v: string) => !v.includes('temp'))
        const tempObj: FeatureStore = {}
        tempArr.map((v: string) => tempObj[v] = this.featureStore[v])
        return tempObj
    }

    /**
     * 可以根据要素id删除要素；
     * @param {string} id  source id
     */
    public delFeatureById = (id: string): void => this.removeLayerAndSource(id)

    /**
     * 清空绘制要素；
     */
    public clearFeatureCollection = (): void => {
        Object.keys(this.featureStore).map((v: string) => this.removeLayerAndSource(v))
        this.endDraw()
        this.featureStore = {}
    }

    /**
     * 可以根据要素id获取到要素数据；
     * @param {string} id  source id
     * @returns {GeoJSON | undefined} 返回一个 Feature 或 无结果
     */
    public getFeatureById = (id: string): GeoJSON | undefined => this.featureStore[id]

    /**
     * 关闭绘制；
     */
    public endDraw = (): true => {
        console.log('退出绘制')
        this.currentMode = EMode.STATIC

        this.mapbox.off('click', this.draw2map)
        this.mapbox.off('mousemove', this.tempLineFollowLine)
        this.mapbox.off('contextmenu', this.endDraw)

        this.removeLayerAndSource('tempLine')
        this.mapbox.getCanvas().style.cursor = '' // 鼠标指针

        this.event.after()

        return true
    }

    /**
     * 添加要素
     * @param {Feature} feature
     * @returns {string | false} false: 添加失败(id 重复, string: 添加成功后的 id
     */
    public add = (feature: Feature): string | false => {
        const { type } = feature.geometry
        const name: string = `add_${type.toLocaleLowerCase()}_${Date.now()}`
        return this.addFeature2Map(name, feature) && name
    }

    /**
     * 更新 feature 中的 properties
     * @param {string} id
     * @param {GeoJsonProperties} options properties 键值对象形式
     * @returns {boolean} false: 更新失败, true: 更新成功
     */
    public update = (id: string, options: GeoJsonProperties): boolean => {
        const feature: Feature | undefined = <Feature>this.featureStore[id]
        if (!feature) return false
        Object.keys(options!).map((k: string) => feature.properties![k] = options![k])
        return true
    }





    /*******************************************************************************
     * 私有 Private
     ********************************************************************************/



    /**
     * 提前中断绘制
     * @param {Array<number>} point 点击坐标
     * @param {string} name id
     * @returns {boolean} false: 继续绘制, true: 中断绘制
     */
    private earlyEnd = (point: Position, name: string): boolean => {
        if (!this.mapbox.getLayer(name)) return false
        const pointFeatures: Array<GeoJSON> = this.mapbox.queryRenderedFeatures(point, { layers: [name] }) // 查找是否存在点
        return pointFeatures.length > 0 ? this.endDraw() : false
    }

    // 进入绘制
    private draw2map = (e: any): void => {
        this.event.draw()
        console.log('绘制模式 ' + this.currentMode)
        const drawLayerName: string = this.drawLayerName
        const lnglat: Position = [e.lngLat.lng, e.lngLat.lat];
        (this.currentMode === EMode.DRAW_POINT) && this.drawPoint(e, drawLayerName, lnglat);// 绘点
        (this.currentMode === EMode.DRAW_LINE_STRING) && this.drawLineString(e, drawLayerName, lnglat);  // 绘线
        (this.currentMode === EMode.DRAW_POLYGON) && this.drawPolygon(e, drawLayerName, lnglat); // 绘面
    }

    // 绘点
    private drawPoint = (e: any, pointLayerName: string, lnglat: Position): void => {
        // console.log(pointLayerName)
        // const pointLayer =  // 查找是否存在 point layer
        // console.log(pointLayer)
        // const pointSource = this.mapbox.getSource(pointLayerName) // 查找是否存在 point Source
        // console.log(pointSource)

        if (this.earlyEnd(e.point, pointLayerName)) return console.log('中断了') // 提前中断绘制

        const getFeatureFn = (): Feature => ({
            type: 'Feature',
            geometry:
                <MultiPoint>{ type: "MultiPoint", coordinates: [lnglat] },
            properties: {}
        })

        this.addSource(pointLayerName, getFeatureFn(), lnglat) // source

        this.add2AllFeature(pointLayerName, getFeatureFn())

        !this.mapbox.getLayer(pointLayerName) && this.addLayer.circle(pointLayerName) // layer

    }

    // 绘线
    private drawLineString = (e: any, lineLayerName: string, lnglat: Position): void => {
        console.log('drawLineString')
        // const clickLnglat: [number, number] = [e.lngLat.lng, e.lngLat.lat] // 点击坐标
        // const lineLayerName: string = this.drawLayerName // 绘制名
        // const tempArr: string[] = lineLayerName.split('_')
        const lineWithPointLayerName: string = 'temp_point_follow_line' + this.drawLayerId

        const getFeatureFn = (): Geometry => ({
            type: 'GeometryCollection',
            geometries: [
                <MultiPoint>{ type: 'MultiPoint', coordinates: [lnglat] },
                <LineString>{ type: 'LineString', coordinates: [lnglat] }
            ],
        })

        if (this.earlyEnd(e.point, lineWithPointLayerName)) return console.log('中断了') // 提前中断绘制

        // 绘线
        this.addSource(lineLayerName, getFeatureFn(), lnglat)
        this.addSource(lineWithPointLayerName, getFeatureFn(), lnglat) // 顶点

        this.add2AllFeature(lineLayerName, getFeatureFn())
        this.add2AllFeature(lineWithPointLayerName, getFeatureFn())

        !this.mapbox.getLayer(lineLayerName) && this.addLayer.line(lineLayerName)
        !this.mapbox.getLayer(lineWithPointLayerName) && this.addLayer.circle(lineWithPointLayerName)

        // 辅助线
        this.addSource('tempLine', getFeatureFn(), lnglat, true)
        !this.mapbox.getLayer('tempLine') && this.addLayer.line('tempLine')
        this.mapbox.on('mousemove', this.tempLineFollowLine)
    }

    // 绘制过程辅助线
    private tempLineFollowLine = (e: any) => {
        const lnglat: Position = [e.lngLat.lng, e.lngLat.lat]
        const { _data: data } = this.mapbox.getSource('tempLine')
        // console.log(data)
        const { type } = data
        if (type === 'GeometryCollection') {
            const index: number = data.geometries.findIndex((v: MultiPoint | LineString) => v.type === 'LineString')
            data.geometries[index].coordinates[1] = lnglat
        } else data.geometry.coordinates[1] = lnglat
        // data.geometry.coordinates.shift()
        // data.geometry.coordinates.push(lnglat)
        // data.geometry.coordinates = [data.geometry.coordinates[1], lnglat]

        // console.log(data)
        this.mapbox.getSource('tempLine').setData(data)
    }

    // 绘面
    private drawPolygon = (e: any, polygonLayerName: string, lnglat: Position): void => {
        const polygonWithPointLayerName: string = 'temp_point_follow_polygon' + this.drawLayerId

        const getFeatureFn = (): Geometry => ({
            type: 'GeometryCollection',
            geometries: [
                <Polygon>{ type: 'Polygon', coordinates: [[lnglat]] },
                <MultiPoint>{ type: 'MultiPoint', coordinates: [lnglat] },
                <LineString>{ type: 'LineString', coordinates: [lnglat] }
            ]
        })

        if (this.earlyEnd(e.point, polygonWithPointLayerName)) return console.log('中断了')// 提前中断绘制

        this.addSource(polygonLayerName, getFeatureFn(), lnglat)
        this.addSource(polygonWithPointLayerName, getFeatureFn(), lnglat) // 顶点

        this.add2AllFeature(polygonLayerName, getFeatureFn())
        this.add2AllFeature(polygonWithPointLayerName, getFeatureFn())

        !this.mapbox.getLayer(polygonLayerName) && this.addLayer.fill(polygonLayerName)
        !this.mapbox.getLayer(polygonWithPointLayerName) && this.addLayer.circle(polygonWithPointLayerName)

        // 辅助线
        !this.mapbox.getSource('tempLine') && this.mapbox.addSource('tempLine', { type: 'geojson', data: getFeatureFn() })
        !this.mapbox.getLayer('tempLine') && this.addLayer.line('tempLine')
        this.mapbox.on('mousemove', this.tempLineFollowLine)
    }





    /**
     * 添加到 feature 总对象中
     * @param {string} name key
     * @param {GeoJSON} Feature value
     */
    private add2AllFeature = (name: string, feature: GeoJSON): false | GeoJSON => !this.featureStore[name] && (this.featureStore[name] = feature)








    /*******************************************************************************
     * mapbox 工具箱
     ********************************************************************************/

    /**
     * map.addLayer
     * @param name source 名
     * @param id layer id ，默认跟 source 同名
     * @param layout layout
     * @param paint paint
     */
    private addLayer = {
        line: (name: string, id?: string, layout?: GeoJsonProperties, paint?: GeoJsonProperties): void => {
            this.mapbox.addLayer({
                id: id || name,
                source: name,
                type: 'line',
                layout: layout || style.LINE_LAYOUT,
                paint: paint || style.LINE_PAINT
            })
        },
        circle: (name: string, id?: string, paint?: GeoJsonProperties): void => {
            this.mapbox.addLayer({
                id: id || name,
                source: name,
                type: 'circle',
                paint: paint || style.POINT_PAINT
            })
        },
        fill: (name: string, id?: string, paint?: GeoJsonProperties): void => {
            this.mapbox.addLayer({
                id: id || name,
                source: name,
                type: 'fill',
                paint: paint || style.FILL_PAINT
            })
        }
    }

    /**
     * removeLayer & source
     * @param name id
     */
    private removeLayerAndSource = (name: String): void => {
        this.mapbox.getLayer(name) && this.mapbox.removeLayer(name)
        this.mapbox.getSource(name) && this.mapbox.removeSource(name)
    }

    /**
     * addSource + Layer
     * @param {string} name
     * @param {Feature} data
     * @return {boolean} true:成功, false:失败
     */
    private addFeature2Map = (name: string, data: Feature): boolean => {
        if (this.mapbox.getSource(name) || this.mapbox.getLayer(name)) return false
        this.addSource(name, data)
        this.add2AllFeature(name, data)
        const { type } = data.geometry
        type.includes('Point') && this.addLayer.circle(name)
        type.includes('LineString') && this.addLayer.line(name)
        type.includes('Polygon') && this.addLayer.fill(name)
        return true
    }

    /**
     * addSource() OR getSource().setData()
     * @param {string} name source id
     * @param {GeoJSON<Geometry | Feature | FeatureCollection>} data Feature 数据
     * @param {Position} lnglat 坐标
     * @param {boolean} useThis true: 直接更新 source, 默认 fasle: 基于原数据修改
     * @returns {boolean} hasSource: false 新增, true 更新
     */
    private addSource = (
        name: string,
        data: GeoJSON,
        lnglat: Position = [],
        useThis: boolean = false
    ): boolean => {
        // const hasSource = <{ _data: GeoJSON }>this.mapbox.getSource(name)
        const hasSource = this.mapbox.getSource(name)
        if (!hasSource) this.mapbox.addSource(name, { type: 'geojson', data }) // 添加 source
        else if (hasSource && useThis) this.mapbox.getSource(name).setData(data) // 直接更新 source
        else if (hasSource && !useThis) { // 在原 source 基础上更新
            const { _data: data } = hasSource;

            // (<GeometryCollection>data).geometries
            //     .map((v: Point | LineString | Polygon | MultiPoint | MultiLineString | MultiPolygon) => {
            //         (<MultiPoint | LineString>v).coordinates.push(lnglat);
            //         (<Polygon>v).coordinates[0].push(lnglat)
            //     });
            // (<Feature>data).geometry.coordinates
            // console.log(data)

            const { type: topType } = data
            const isGeometryCollection = topType === 'GeometryCollection' // 是否组合类型

            if (!isGeometryCollection) {

                data.geometry.type.includes('Polygon') // 是否多边形
                    ? data.geometry.coordinates[0].push(lnglat)
                    : data.geometry.coordinates.push(lnglat)

            } else if (isGeometryCollection) {

                data.geometries.map((v: MultiPoint | LineString | Polygon) => {
                    v.type.includes('Polygon') // 是否多边形
                        ? (<Polygon>v).coordinates[0].push(lnglat) // 三维数组
                        : (<MultiPoint | LineString>v).coordinates.push(lnglat); // 二维数组
                })

            }
            // console.log(data)
            this.mapbox.getSource(name).setData(data)
        }
        return hasSource === undefined
    }
}